# ~*~ coding: utf-8 ~*~
import os
import locale
import atexit
import json
from importlib import import_module

from flask import Flask, redirect, url_for, render_template
from flask_wtf.csrf import CSRFError
from flask_login import login_required

from apps import commands
from apps.extensions import db, migrate, csrf, cache, login_manager, celery, socketio
from apps.users.models import User, AnonymousUser
from apps.blueprints import all_blueprints
from apps.common.response import csrf_error, permissions_error
from apps.common.request import is_ajax
from apps.terminal.service.ws import SSHSocketIO, SFTPSocketIO
from apps.codes.service.ws import CodesSocketIO


def create_app(config_object):
    flask_app = Flask(__name__, static_folder='static', template_folder='templates')
    flask_app.config.from_object(config_object)

    register_extensions(flask_app)
    register_blueprint(flask_app)
    register_middleware(flask_app)
    register_commands(flask_app)
    register_logging(flask_app)

    celery_app = register_celery(flask_app)
    return flask_app, celery_app


def register_celery(app):
    # register celery
    celery_app = celery.init_app(app)

    # push app context
    app.app_context().push()

    # init celery signal
    import_module('apps.ops.celery.signal_handler')
    celery.autodiscover_tasks(packages=lambda: [task for task in app.config.get('CELERY_TASKS_IMPORTS', list())])

    # register celery beat task
    # for beat_task in app.config.get('BEAT_TASK_LIST', list()):
    #     celery.conf.beat_schedule.update(beat_task)
    return celery_app


def register_extensions(app):
    # import DB models.
    with app.app_context():
        for module in app.config.get('DB_MODELS_IMPORTS', list()):
            import_module('{}.{}'.format(module, 'models'))

    # register SQLAlchemy
    db.app = app
    db.init_app(app)

    # register migrate
    migrate_kwargs = {
        'compare_type': True,  # 检查字段类型
        'compare_server_default': True  # 比较默认值
    }
    migrate.init_app(app, db, directory='apps/migrations', **migrate_kwargs)

    # register cache
    cache.init_app(app)

    # register csrt
    @app.errorhandler(CSRFError)
    def csrf_error_handler(e):
        return csrf_error({
            'message': 'CSRF验证失败'
        })

    csrf.init_app(app)

    # register login manager
    login_manager.session_protection = 'strong'
    login_manager.anonymous_user = AnonymousUser

    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(user_id)

    @login_manager.unauthorized_handler
    def unauthorized():
        if is_ajax():
            return permissions_error({
                'message': '请登录后在访问在资源',
                'type': 'login'
            })
        else:
            return redirect(url_for('users.login'))

    login_manager.init_app(app)

    # register socket-io
    socketio.init_app(app, async_mode='gevent')
    socketio.on_namespace(SSHSocketIO(namespace='/ssh'))
    socketio.on_namespace(SFTPSocketIO(namespace='/sftp'))
    socketio.on_namespace(CodesSocketIO(namespace='/codes'))

    # # 注册python解释器退出时执行的函数
    # atexit.register(close_session)


def register_blueprint(app):
    # Setup redirects and register blueprints.
    app.add_url_rule('/favicon.ico', 'favicon', lambda: app.send_static_file('favicon.ico'))
    for bp in all_blueprints:
        import_module(bp.import_name)
        app.register_blueprint(bp)

    @app.route('/', endpoint='index')
    @login_required
    def index():
        return render_template('index.html')

    @app.route('/api/health/', endpoint='health')
    def health():
        return json.dumps({'status': 200, 'message': 'ok'})


def register_middleware(app):
    with app.app_context():
        import_module('apps.middleware')


def register_commands(app):
    """Register Click commands."""
    app.cli.add_command(commands.test)
    app.cli.add_command(commands.lint)
    app.cli.add_command(commands.clean)
    app.cli.add_command(commands.urls)
    app.cli.add_command(commands.init)


def register_logging(app):
    import logging
    from logging.handlers import RotatingFileHandler

    # log level
    log_level = app.config.get('LOG_LEVEL')

    # log dir
    if not os.path.exists(app.config['LOG_PATH']):
        os.makedirs(app.config['LOG_PATH'])
    log_path = os.path.join(app.config['LOG_PATH'], '{}.log'.format(log_level))

    # debug -- info -- warning -- error -- critical
    formatter = logging.Formatter(
        '%(asctime)s %(levelname)s %(pathname)s %(lineno)s %(module)s.%(funcName)s %(message)s'
    )

    if log_level == 'debug':
        # create console handler and set level to debug and set formatter
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        console_handler.setLevel(logging.DEBUG)
        app.logger.addHandler(console_handler)
        # create file handler and set level to debug and set formatter
        file_handler = RotatingFileHandler(filename=log_path)
        file_handler.setFormatter(formatter)
        file_handler.setLevel(logging.DEBUG)
        app.logger.addHandler(file_handler)
    elif log_level == 'info':
        file_handler = RotatingFileHandler(filename=log_path)
        file_handler.setFormatter(formatter)
        file_handler.setLevel(logging.INFO)
        app.logger.addHandler(file_handler)
    elif log_level == 'warning':
        file_handler = RotatingFileHandler(filename=log_path)
        file_handler.setFormatter(formatter)
        file_handler.setLevel(logging.WARNING)
        app.logger.addHandler(file_handler)
    elif log_level == 'error':
        file_handler = RotatingFileHandler(filename=log_path)
        file_handler.setFormatter(formatter)
        file_handler.setLevel(logging.ERROR)
        app.logger.addHandler(file_handler)
    elif log_level == 'critical':
        file_handler = RotatingFileHandler(filename=log_path)
        file_handler.setFormatter(formatter)
        file_handler.setLevel(logging.CRITICAL)
        app.logger.addHandler(file_handler)
    else:
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        console_handler.setLevel(logging.DEBUG)
        app.logger.addHandler(console_handler)
